﻿/* Copyright (c) 2009, 2010, John Cronin
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */

using System;
using System.Collections.Generic;
using System.Text;

namespace tysila
{
    partial class x86_64_Assembler
    {
        IEnumerable<OutputBlock> x86_64_assign_i8_gprptr_label(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // mov tempreg, [label wrt rip]
            // optional (if constant_offset != 0): lea tempreg/destreg, [tempreg + offset]
            // optional (if assigning to contents of/stackloc): mov [destreg], tempreg

            x86_64_gpr tempreg;
            x86_64_gpr saved_reg = null;
            x86_64_gpr destreg = null;
            bool to_contents_of = false;
            bool is_stack_loc = false;

            List<OutputBlock> ret = new List<OutputBlock>();

            if ((result.hardware_loc is hardware_contentsof) || (result.hardware_loc is hardware_stackloc))
            {
                if (result.hardware_loc is hardware_contentsof)
                    destreg = ((hardware_contentsof)result.hardware_loc).base_loc as x86_64_gpr;
                else
                    is_stack_loc = true;

                tempreg = Rdi;

                if (tempreg == null)
                {
                    if (is_stack_loc)
                        saved_reg = Rdx;
                    else
                    {
                        if (destreg == Rdx)
                            saved_reg = Rcx;
                        else
                            saved_reg = Rdx;
                    }
                    tempreg = new x86_64_gpr { reg = saved_reg.reg };
                    ret.Add(new CodeBlock { Code = this.SaveLocation(saved_reg) });
                }
                to_contents_of = true;
            }
            else
                tempreg = destreg = result.hardware_loc as x86_64_gpr;

            // do mov tempreg, [label wrt rip]
            ret.Add(new CodeBlock
            {
                Code = EncOpcode(tempreg, Rbp, 0, true, 0, 0x8b)
            });
            ret.Add(new RelocationBlock
            {
                Target = op1.label,
                RelType =
                    ElfLib.Elf64_Rela_Shdr.Elf64_Rela.RelocationType.R_X86_64_GOTPCREL,
                Size = 4,
                Value = -4
            });

            if (op1.constant_offset != 0)
                // do lea tempreg, [tempreg + offset]
                ret.Add(new CodeBlock { Code = EncOpcode(tempreg, tempreg, 0, true, op1.constant_offset, 0x8d) });

            if (to_contents_of)
            {
                // mov [destreg], tempreg
                hardware_location destloc;
                int offset;

                if (is_stack_loc)
                {
                    destloc = result.hardware_loc;
                    offset = 0;
                }
                else
                {
                    destloc = destreg;
                    offset = ((hardware_contentsof)result.hardware_loc).const_offset;
                }
                ret.Add(new CodeBlock
                {
                    Code = EncOpcode(tempreg, destloc, 1, true, offset, 0x89)
                });

                /*if (saved_reg == null)
                    state.reg_alloc.FreeRegister(tempreg);
                else
                    ret.Add(new CodeBlock { Code = this.RestoreLocation(saved_reg) });*/
            }

            return ret;
        }

        IEnumerable<OutputBlock> x86_64_assign_i8_sptr_dgpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // lea dest, [src]
            return OBList(EncOpcode(result.hardware_loc, ((hardware_addressof)op1.hardware_loc).base_loc, 0, true, 0, 0x8d));
        }

        IEnumerable<OutputBlock> x86_64_assign_i4_gpr_gprmem(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            return OBList(EncOpcode(result.hardware_loc, op1.hardware_loc, 3, false, 0, 0x8b));
        }

        IEnumerable<OutputBlock> x86_64_assign_i4_gprmem_gpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            return OBList(EncOpcode(op1.hardware_loc, result.hardware_loc, 3, false, 0, 0x89));
        }

        IEnumerable<OutputBlock> x86_64_assign_i4_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            List<byte> ret = new List<byte>();
            if (((x86_64_gpr)result.hardware_loc).is_extended)
                ret.Add(RexB(true));
            ret.Add((byte)(0xb8 + ((x86_64_gpr)result.hardware_loc).base_val));
            ret.AddRange(ToByteArray(op1.constant_val));
            return OBList(ret);
        }

        IEnumerable<OutputBlock> x86_64_assign_i4_gprmem_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            return OBList(EncOpcode(0, result.hardware_loc, 3, false, 0, 0xc7), ToByteArray(Convert.ToInt32(op1.constant_val)));
        }

        IEnumerable<OutputBlock> x86_64_assign_i8_gpr_gprmem(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            return OBList(EncOpcode(result.hardware_loc, op1.hardware_loc, 3, true, 0, 0x8b));
        }

        IEnumerable<OutputBlock> x86_64_assign_i8_gprmem_gpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            return OBList(EncOpcode(op1.hardware_loc, result.hardware_loc, 3, true, 0, 0x89));
        }

        IEnumerable<OutputBlock> x86_64_assign_i8_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            List<byte> ret = new List<byte>();
            ret.Add((byte)(RexW(true) | RexB(((x86_64_gpr)result.hardware_loc).is_extended)));
            ret.Add((byte)(0xb8 + ((x86_64_gpr)result.hardware_loc).base_val));
            ret.AddRange(ToByteArray(op1.constant_val));
            return OBList(ret);
        }

        IEnumerable<OutputBlock> x86_64_peek_u1_gpr_gpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // movzx destreg, [srcreg]
            return OBList(EncOpcode(result.hardware_loc, op1.hardware_loc, 0, true, 0, 0x0f, 0xb6));
        }

        IEnumerable<OutputBlock> x86_64_peek_u2_gpr_gpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // movzx destreg, [srcreg]
            return OBList(EncOpcode(result.hardware_loc, op1.hardware_loc, 0, true, 0, 0x0f, 0xb7));
        }

        IEnumerable<OutputBlock> x86_64_peek_i1_gpr_gpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // movsx destreg, [srcreg]
            return OBList(EncOpcode(result.hardware_loc, op1.hardware_loc, 0, true, 0, 0x0f, 0xbe));
        }

        IEnumerable<OutputBlock> x86_64_peek_i2_gpr_gpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // movsx destreg, [srcreg]
            return OBList(EncOpcode(result.hardware_loc, op1.hardware_loc, 0, true, 0x0f, 0xbf));
        }

        IEnumerable<OutputBlock> x86_64_peek_u1_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of a specified hardware address to a register + zero extend to int32

            // Do mov destreg, imm64; movzx destreg, [destreg]
            return OBList(EncAddOpcode(result.hardware_loc as x86_64_gpr, true, 0xb8,
                ToByteArray(Convert.ToUInt64(op1.constant_val))),
                EncOpcode(result.hardware_loc, result.hardware_loc, 0, true, 0, 0x0f, 0xb6));
        }

        IEnumerable<OutputBlock> x86_64_peek_u2_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of a specified hardware address to a register + zero extend to int32

            // Do mov destreg, imm64; movzx destreg, [destreg]
            return OBList(EncAddOpcode(result.hardware_loc as x86_64_gpr, true, 0xb8,
                ToByteArray(Convert.ToUInt64(op1.constant_val))),
                EncOpcode(result.hardware_loc, result.hardware_loc, 0, true, 0, 0x0f, 0xb7));
        }

        IEnumerable<OutputBlock> x86_64_peek_i1_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of a specified hardware address to a register + sign extend to int32

            // Do mov destreg, imm64; movsx destreg, [destreg]
            return OBList(EncAddOpcode(result.hardware_loc as x86_64_gpr, true, 0xb8,
                ToByteArray(Convert.ToUInt64(op1.constant_val))),
                EncOpcode(result.hardware_loc, result.hardware_loc, 0, true, 0, 0x0f, 0xbe));
        }

        IEnumerable<OutputBlock> x86_64_peek_i2_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of a specified hardware address to a register + sign extend to int32

            // Do mov destreg, imm64; movsx destreg, [destreg]
            return OBList(EncAddOpcode(result.hardware_loc as x86_64_gpr, true, 0xb8,
                ToByteArray(Convert.ToUInt64(op1.constant_val))),
                EncOpcode(result.hardware_loc, result.hardware_loc, 0, true, 0, 0x0f, 0xbf));
        }


        IEnumerable<OutputBlock> x86_64_peek_u4_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of a specified hardware address to a register + sign extend to int32

            // Do mov destreg, imm64; mov destreg, [destreg]
            return OBList(EncAddOpcode(result.hardware_loc as x86_64_gpr, true, 0xb8,
                ToByteArray(Convert.ToUInt64(op1.constant_val))),
                EncOpcode(result.hardware_loc, result.hardware_loc, 0, false, 0, 0x8b));
        }

        IEnumerable<OutputBlock> x86_64_peek_u8_gpr_const(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of a specified hardware address to a register + sign extend to int32

            // Do mov destreg, imm64; movsx destreg, [destreg]
            return OBList(EncAddOpcode(result.hardware_loc as x86_64_gpr, true, 0xb8,
                ToByteArray(Convert.ToUInt64(op1.constant_val))),
                EncOpcode(result.hardware_loc, result.hardware_loc, 0, true, 0, 0x8b));
        }

        IEnumerable<OutputBlock> x86_64_peek_r48_dxmm_sgpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of the address specified in the source register to an xmm

            return OBList(EncOpcode(result.hardware_loc, op1.hardware_loc, 0, false, 0, (byte)((op == ThreeAddressCode.Op.peek_r4) ? 0xf3 : 0xf2), 0x0f, 0x10));
        }

        IEnumerable<OutputBlock> x86_64_poke_r48_o1gpr_o2xmm(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of an xmm to an address pointed to by a gpr

            return OBList(EncOpcode(op2.hardware_loc, op1.hardware_loc, 0, false, 0, (byte)((op == ThreeAddressCode.Op.peek_r4) ? 0xf3 : 0xf2), 0x0f, 0x11));
        }

        IEnumerable<OutputBlock> x86_64_peek_u_gpr_gpr(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of the address in op1 to the result

            switch (op)
            {
                case ThreeAddressCode.Op.peek_u:
                case ThreeAddressCode.Op.peek_u8:
                case ThreeAddressCode.Op.ldobj_i8:
                case ThreeAddressCode.Op.ldobj_i:
                    return OBList(EncOpcode(result, op1, 0, true, 0, false, 0x8b));
                case ThreeAddressCode.Op.peek_u4:
                case ThreeAddressCode.Op.ldobj_i4:
                    return OBList(EncOpcode(result, op1, 0, false, 0, false, 0x8b));
                case ThreeAddressCode.Op.peek_u2:
                    return OBList(new byte[] { 0x66 }, EncOpcode(result, op1, 0, false, 0, false, 0x8b));
                case ThreeAddressCode.Op.peek_u1:
                    return OBList(EncOpcode(result, op1, 0, false, 0, true, 0x8a));
                default:
                    throw new NotSupportedException();
            }
        }

        IEnumerable<OutputBlock> x86_64_poke_u1_gprconst_gprmemconst(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            // Move the contents of op2 to the address op1

            // Mov tempreg, imm64; mov [tempreg], srcreg/const

            List<OutputBlock> obs = new List<OutputBlock>();
            x86_64_gpr tr = Rdi;
            if (op1.type == var.var_type.Const)
            {
                obs.Add(new CodeBlock
                {
                    Code = EncAddOpcode(tr, true, 0xb8, ToByteArray(Convert.ToUInt64(op1.constant_val)))
                });
            }
            else
            {
                obs.Add(new CodeBlock
                {
                    Code = EncOpcode(tr, op1.hardware_loc, 3, true, 0, 0x8b)
                });
            }
            if (op2.type == var.var_type.Const)
            {
                switch (op)
                {
                    case ThreeAddressCode.Op.poke_u1:
                        obs.Add(new CodeBlock { Code = EncOpcode(0, tr, 0, false, 0, true, 0xc6) });
                        //obs.Add(new CodeBlock { Code = ToByteArray(Convert.ToByte(op2.constant_val)) });
                        obs.Add(new CodeBlock { Code = ToByteArrayZeroExtend(op2.constant_val, 1) });
                        break;
                    case ThreeAddressCode.Op.poke_u2:
                        obs.Add(new CodeBlock { Code = new byte[] { 0x66 } });
                        obs.Add(new CodeBlock { Code = EncOpcode(0, tr, 0, false, 0, 0xc7) });
                        //obs.Add(new CodeBlock { Code = ToByteArray(Convert.ToUInt16(op2.constant_val)) });
                        obs.Add(new CodeBlock { Code = ToByteArrayZeroExtend(op2.constant_val, 2) });
                        break;
                    case ThreeAddressCode.Op.poke_u4:
                        obs.Add(new CodeBlock { Code = EncOpcode(0, tr, 0, false, 0, 0xc7) });
                        //obs.Add(new CodeBlock { Code = ToByteArray(Convert.ToUInt32(op2.constant_val)) });
                        obs.Add(new CodeBlock { Code = ToByteArrayZeroExtend(op2.constant_val, 4) });
                        break;
                    case ThreeAddressCode.Op.poke_u8:
                    case ThreeAddressCode.Op.poke_u:
                        obs.Add(new CodeBlock { Code = EncOpcode(0, tr, 0, true, 0, 0xc7) });
                        //obs.Add(new CodeBlock { Code = ToByteArray(Convert.ToUInt64(op2.constant_val)) });
                        obs.Add(new CodeBlock { Code = ToByteArrayZeroExtend(op2.constant_val, 8) });
                        break;
                }
            }
            else
            {
                switch (op)
                {
                    case ThreeAddressCode.Op.poke_u1:
                        obs.Add(new CodeBlock { Code = EncOpcode(op2.hardware_loc, tr, 0, false, 0, true, 0x88) });
                        break;
                    case ThreeAddressCode.Op.poke_u2:
                        obs.Add(new CodeBlock { Code = new byte[] { 0x66 } });
                        obs.Add(new CodeBlock { Code = EncOpcode(op2.hardware_loc, tr, 0, false, 0, 0x89) });
                        break;
                    case ThreeAddressCode.Op.poke_u4:
                        obs.Add(new CodeBlock { Code = EncOpcode(op2.hardware_loc, tr, 0, false, 0, 0x89) });
                        break;
                    case ThreeAddressCode.Op.poke_u8:
                    case ThreeAddressCode.Op.poke_u:
                        obs.Add(new CodeBlock { Code = EncOpcode(op2.hardware_loc, tr, 0, true, 0, 0x89) });
                        break;
                }
            }
            return obs;
        }

        IEnumerable<OutputBlock> x86_64_portout_u2_u1_const_rax(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        {
            return OBList(new List<byte> { 0xe6 }, ToByteArray(Convert.ToByte(op1.constant_val)));
        }

        IEnumerable<OutputBlock> x86_64_portout_u2_u1_dx_rax(ThreeAddressCode.Op op, var result, var op1, var op2,
            ThreeAddressCode tac, AssemblerState state)
        { return OBList(new List<byte> { 0xee }); }

        IEnumerable<OutputBlock> x86_64_stobj_vt(ThreeAddressCode.Op op, var result, var op1, var op2, ThreeAddressCode tac, AssemblerState state)
        {
            if (tac.VTSize.HasValue == false)
                throw new Exception("VTSize not assigned");
            List<OutputBlock> ret = new List<OutputBlock>();

            // Store the value in op2 to the address in op1

            if (!op2.hardware_loc.Equals(Rsi))
            {
                // Load the address of the value in op2 to Rsi
                ret.AddRange(OBList(EncOpcode(Rsi, op2.hardware_loc, 0, true, 0, 0x8d)));
            }

            int vt_size = tac.VTSize.Value;
            int cur_offset = 0;
            int cur_reps = 0;
            int cur_leftover = 0;
            int cur_blocksize = 8;

            /* do the move
             * 
             * dest address is in Rdi, source in Rsi */
            do
            {
                cur_reps = (vt_size - cur_offset) / cur_blocksize;
                cur_leftover = vt_size - cur_offset - cur_reps * cur_blocksize;

                /* store cur_reps to rcx */
                ret.AddRange(OBList(EncOpcode(0, Rcx, 3, true, 0, 0xc7),
                    ToByteArray(cur_reps)));

                /*do rep movsX */
                switch (cur_blocksize)
                {
                    case 8:
                        ret.AddRange(OBList(new byte[] { 0xf3, 0x48, 0xa5 }));
                        break;
                    case 4:
                        ret.AddRange(OBList(new byte[] { 0xf3, 0xa5 }));
                        break;
                    case 2:
                        ret.AddRange(OBList(new byte[] { 0x66, 0xf3, 0xa5 }));
                        break;
                    case 1:
                        ret.AddRange(OBList(new byte[] { 0xf3, 0xa4 }));
                        break;
                    default:
                        throw new NotSupportedException();
                }

                cur_offset += cur_reps * cur_blocksize;
                cur_blocksize /= 2;
            } while (cur_leftover > 0);

            return ret;
        }

        IEnumerable<OutputBlock> x86_64_assign_vt(ThreeAddressCode.Op op, var result, var op1, var op2, ThreeAddressCode tac, AssemblerState state)
        {
            if (tac.VTSize.HasValue == false)
                throw new Exception("VTSize not assigned");

            int vt_size = tac.VTSize.Value;
            int cur_offset = 0;
            int cur_reps = 0;
            int cur_leftover = 0;
            int cur_blocksize = 8;
            List<OutputBlock> ret = new List<OutputBlock>();

            x86_64_gpr rcx = state.reg_alloc.GetRegister(CRcx, var.Coerce()) as x86_64_gpr;
            x86_64_gpr temp_reg = null;
            if (rcx == null)
            {
                /* store rcx to temp_reg */
                temp_reg = state.reg_alloc.GetTemporaryRegister() as x86_64_gpr;
                ret.AddRange(OBList(EncOpcode(temp_reg, Rcx, 3, true, 0, 0x8b)));
                rcx = Rcx;
            }

            /* do the move
             * 
             * dest address is in Rdi, source in Rsi */
            do
            {
                cur_reps = (vt_size - cur_offset) / cur_blocksize;
                cur_leftover = vt_size - cur_offset - cur_reps * cur_blocksize;

                /* store cur_reps to rcx */
                ret.AddRange(OBList(EncOpcode(0, Rcx, 3, true, 0, 0xc7),
                    ToByteArray(cur_reps)));

                /*do rep movsX */
                switch (cur_blocksize)
                {
                    case 8:
                        ret.AddRange(OBList(new byte[] { 0xf3, 0x48, 0xa5 }));
                        break;
                    case 4:
                        ret.AddRange(OBList(new byte[] { 0xf3, 0xa5 }));
                        break;
                    case 2:
                        ret.AddRange(OBList(new byte[] { 0x66, 0xf3, 0xa5 }));
                        break;
                    case 1:
                        ret.AddRange(OBList(new byte[] { 0xf3, 0xa4 }));
                        break;
                    default:
                        throw new NotSupportedException();
                }

                cur_offset += cur_reps * cur_blocksize;
                cur_blocksize /= 2;
            } while (cur_leftover > 0);



            if (temp_reg != null)
            {
                /* restore rcx */
                ret.AddRange(OBList(EncOpcode(temp_reg, Rcx, 3, true, 0, 0x89)));
            }
            else
                state.reg_alloc.FreeRegister(rcx);

            return ret;            
        }
    }
}
